package com.code.soulnetty.modbus.client.codec;

import com.code.soulnetty.modbus.biz.ModbusUtils;
import com.code.soulnetty.modbus.biz.resp.ProtocolReadResponse;
import com.code.soulnetty.modbus.client.channel.impl.ProtocolChannel;
import com.code.soulnetty.modbus.client.channel.impl.ProtocolChannelCache;
import com.code.soulnetty.modbus.client.message.ProtocolRequest;
import com.code.soulnetty.modbus.client.message.ProtocolRequestWrap;
import com.code.soulnetty.modbus.client.message.ProtocolResponse;
import com.code.soulnetty.modbus.client.service.CrcUtils;
import com.code.soulnetty.modbus.client.service.FunctionCode;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;

import java.net.SocketAddress;

/**
 * modbus Pdu部分解析
 *
 **/
@Slf4j
public abstract class ModbusCodec implements Codec<ByteBuf, ProtocolResponse> {
    @Override
    public ProtocolResponse decode(ChannelHandlerContext ctx, ByteBuf header, ByteBuf in) {
        int curIndex = in.readerIndex();
        //功能码
        final byte fc = in.readByte();
        final int realFc = (0xff & fc) & (0xff >>> 1);
        final SocketAddress socketAddress = ctx.channel().remoteAddress();
        final ProtocolChannel protocolChannel = ProtocolChannelCache.get(socketAddress);
        if (protocolChannel == null) {
            return ProtocolResponse.fail("channel is null");
        }
        final String msgId = msgId(header) + "_" + realFc;
        final ProtocolRequest request = protocolChannel.request(msgId);
        if (request == null) {
            return ProtocolResponse.fail(msgId, "request timeout " + ctx.channel().remoteAddress());
        }
        //异常情况
        if (fc < 0) {
            return ProtocolResponse.fail(msgId, "error data is " + ByteBufUtil.hexDump(in));
        }
        in.readerIndex(curIndex);
        final byte[] data = new byte[in.readableBytes()];
        in.readBytes(data);
        if (check(header, data)) {
            return ProtocolResponse.fail(msgId, "crc 校验失败 :" + ByteBufUtil.hexDump(data));
        }
        //读请求结果
        if (FunctionCode.isRead(fc)) {
            ProtocolReadResponse response = ModbusUtils.decodeResponse((ProtocolRequestWrap) request, data, 2);
            response.setMsgId(msgId);
            return response;
        }
        throw new RuntimeException("fc not exists " + fc);
    }

    private boolean check(ByteBuf header, byte[] data) {
        return checkCrc16() && checkCrc16(header, data);
    }

    private boolean checkCrc16(ByteBuf header, byte[] data) {
        int headerIndex = header.readerIndex();
        byte[] headerData = new byte[header.readableBytes()];
        header.readBytes(headerData);
        header.readerIndex(headerIndex);
        byte[] allData = new byte[headerData.length + data.length - 2];
        System.arraycopy(headerData, 0, allData, 0, headerData.length);
        System.arraycopy(data, 0, allData, headerData.length, data.length - 2);
        final byte[] crc16 = CrcUtils.crc16ToByte(allData);
        //crc校验失败
        if (crc16[0] != data[data.length - 2] || crc16[1] != data[data.length - 1]) {
            log.error("crc16 error {},{}", ByteBufUtil.hexDump(allData), ByteBufUtil.hexDump(crc16));
            return true;
        }
        return false;
    }

    /**
     * 获取msgId
     *
     * @return
     */
    public abstract String msgId(ByteBuf header);

    /**
     * 是否做crc16校验
     *
     * @return
     */
    public abstract boolean checkCrc16();

}
